package edu.hrbu.systemadmin.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import edu.hrbu.systemadmin.entity.*;
import edu.hrbu.systemadmin.exception.NotFoundUserException;
import edu.hrbu.systemadmin.exception.PasswordIncorrectException;
import edu.hrbu.systemadmin.mapper.YdlUserMapper;
import edu.hrbu.systemadmin.service.*;
import edu.hrbu.systemadmin.vo.LoginUserVo;
import edu.hrbu.systemadmin.vo.UserPageVo;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static edu.hrbu.systemadmin.util.ConstUtil.*;

/**
 * <p>
 * 用户信息表 服务实现类
 * </p>
 *
 * @author 徐登宇
 * @since 2023-04-22
 */
@Service
@Slf4j
public class YdlUserServiceImpl extends ServiceImpl<YdlUserMapper, YdlUser> implements IYdlUserService {

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private IYdlUserRoleService userRoleService;

    @Resource
    private IYdlRoleService roleService;

    @Resource
    private IYdlRoleMenuService roleMenuService;

    @Resource
    private IYdlMenuService menuService;

    /**
     * 登录
     * 将登录信息存到redis
     * ttl：30min
     * key：token
     * value：vo
     *
     * @param userName 用户名
     * @param password 密码
     * @return vo
     */
    @Override
    public LoginUserVo login(String userName, String password) {

        LambdaQueryWrapper<YdlUser> wrapperYdlUser = new LambdaQueryWrapper<>();
        // 判断是否有该用户（通过用户名）
        wrapperYdlUser.eq(YdlUser::getUserName, userName);
        YdlUser dbUser = getOne(wrapperYdlUser);
        if (dbUser == null) {
            // 数据库中没有该用户
            log.info("登录操作：【" + userName + "】该用户不存在");
            throw new NotFoundUserException("登录操作：【" + userName + "】该用户不存在");
        }

        if (!password.equals(dbUser.getPassword())) {
            // 登录密码错误
            log.info("登录操作：【" + password + "】该用户登录密码错误");
            throw new PasswordIncorrectException("登录操作：【" + password + "】该用户登录密码错误");
        }


        HttpServletRequest request = getRequest();
        UserAgent userAgent = new UserAgent(request.getHeader("User-Agent"));
        String ip = request.getRemoteHost();

        ResponseEntity<String> forEntity = restTemplate.getForEntity("https://whois.pconline.com.cn/ipJson.jsp?ip=" + request.getRemoteHost() + "&json=true", String.class);

        Map<String, Object> map = BeanUtil.beanToMap(forEntity);
//        log.error("map:{}",map);
        String location = (String) map.get("region") + map.get("pro") + map.get("city");

        String token = UUID.randomUUID().toString();
        // 将user封装成LoginUserVo
        LoginUserVo loginUserVo = LoginUserVo.builder()
                .userId(dbUser.getUserId())
                .token(token)
                .loginTime(new Date())
                .ipaddr(ip)
                .loginLocation(location)
                .browser(userAgent.getBrowser().getName())
                .os(userAgent.getOperatingSystem().getName())
                .ydlUser(dbUser)
                .build();

        // 先判断是否该用户有登录状态，有登录状态先将登录状态删除
        String tokenKeyPrefix = TOKEN_PREFIX + userName + ":";
        Set<String> keys = stringRedisTemplate.keys(tokenKeyPrefix + "*");
        // 将该用户曾经的登录凭证删除（实现单点登录）
        if (keys != null && keys.size() > 0) {
//            keys.forEach(keys::remove);
            // 从redis中删除！！！
            keys.forEach(k -> stringRedisTemplate.delete(k));
        }

//        log.error("{}",loginUserVo);
        // 将用户信息存到redis当中【TOKEN:admin:uuid+ => 这样我们就能保证每个用户只在一个设备上保持登录】
        String tokenKey = tokenKeyPrefix + token;

        // key：token；value：vo
        stringRedisTemplate.opsForValue().set(tokenKey, JSONUtil.toJsonStr(loginUserVo), HALF_TTL, TimeUnit.MINUTES);

        return loginUserVo;
    }


    /**
     * 登出
     * 删除redis中的token、perms、roles：
     *   tokenkey怎么获取，通过请求头中获取
     */
    @Override
    public void logout() {
        // 获取请求头中的token（拼上前缀就是redis中token的key）
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        String token = request.getHeader(HEADER_AUTHORIZATION);
        String tokenKeyPrefix = TOKEN_PREFIX + request.getHeader(HEADER_USERNAME) + ":";
        String tokenKey = tokenKeyPrefix + token;
        String permsKey=PERMS_PREFIX+token;
        String rolesKey=ROLE_PREFIX+token;
        // 删除redis中的token
        stringRedisTemplate.delete(tokenKey);
        // 删除redis中的perms
        stringRedisTemplate.delete(permsKey);
        // 删除redis中的roles
        stringRedisTemplate.delete(rolesKey);
    }

    // 条件分页查询
    @Override
    public Page<YdlUser> pageQueryWithCondition(UserPageVo userPageVo) {
        Page<YdlUser> pageParam = new Page<>(userPageVo.getPage(), userPageVo.getSize());
        LambdaQueryWrapper<YdlUser> wrapperYdlUser = new LambdaQueryWrapper<>();
        if (StrUtil.isNotBlank(userPageVo.getUserName()))
            wrapperYdlUser.like(YdlUser::getUserName, userPageVo.getUserName());
        if (StrUtil.isNotBlank(userPageVo.getNickName()))
            wrapperYdlUser.like(YdlUser::getNickName, userPageVo.getNickName());
        Page<YdlUser> pageModel = page(pageParam, wrapperYdlUser);
//        log.info("pageModel:{}",pageModel.getTotal());
        return pageModel;
    }

    // 根据userId获取权限和菜单列表
    @Override
    public Map<String, List> info(LoginUserVo loginUserVo) {
        HashMap<String, List> map = new HashMap<>();
        ArrayList<YdlRole> ownRoleList = new ArrayList<>();
        ArrayList<YdlMenu> ownMenuList = new ArrayList<>();

        // 获取信息


        // 获取所有的权限列表
        List<YdlRole> allRoleList = roleService.list();
        // 根据userId获取对应的roleId列表
        LambdaQueryWrapper<YdlUserRole> wrapperUserRole = new LambdaQueryWrapper<>();
        List<Long> ownRoleIdList = userRoleService.list(
                        wrapperUserRole.eq(YdlUserRole::getUserId, loginUserVo.getYdlUser().getUserId()))
                .stream().map(YdlUserRole::getRoleId).collect(Collectors.toList());
        // 获取对应的权限列表
        for (YdlRole role : allRoleList) {
            if (ownRoleIdList.contains(role.getRoleId())) {
                ownRoleList.add(role);
            }
        }
        List<String> roles = ownRoleList.stream().map(YdlRole::getRoleTag).collect(Collectors.toList());
        map.put("roles", roles);
        // 存到redis中，ttl：30min
        stringRedisTemplate.opsForValue().set(ROLE_PREFIX + loginUserVo.getToken(), JSONUtil.toJsonStr(roles), HALF_TTL, TimeUnit.MINUTES);

        // 获取所有的menuList
        List<YdlMenu> allMenuList = menuService.list();
        // 根据ownRoleIdList获取ownMenuIdList
        LambdaQueryWrapper<YdlRoleMenu> wrapperRoleMenu = new LambdaQueryWrapper<>();
        List<Long> ownMenuIdList = roleMenuService.list(wrapperRoleMenu.in(YdlRoleMenu::getRoleId, ownRoleIdList))
                .stream().map(YdlRoleMenu::getMenuId).collect(Collectors.toList());
        for (YdlMenu menu : allMenuList) {
            if (ownMenuIdList.contains(menu.getMenuId())) {
                ownMenuList.add(menu);
            }
        }
        List<String> perms = ownMenuList.stream().map(YdlMenu::getPerms).collect(Collectors.toList());
        map.put("perms", perms);
        // 存到redis中，ttl：30min
        stringRedisTemplate.opsForValue().set(PERMS_PREFIX + loginUserVo.getToken(), JSONUtil.toJsonStr(perms), HALF_TTL, TimeUnit.MINUTES);

        return map;
    }
}
